/**
 * 验证器
 * @author luguoxiang
 * @date 2023-12-13
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
/**
 * 验证结果
 */
type Result = boolean | string | Promise<void>;

/**
 * 验证配置对象
 */
type RuleObj = {
  [k: string]: any;
  /**
   * 错误信息
   */
  msg?: string;
  /**
   * 校验数组（嵌套校验）
   */
  array?: { [k: string]: Rule };
  /**
   * 校验对象（嵌套校验）
   */
  object?: { [k: string]: Rule };
  /**
   * 自定义校验
   * @param value
   * @param context
   */
  validator?(value: any, context?: { data: any; rule: any }): Result;
  /**
   * 正则校验
   */
  pattern?: string | RegExp;
  /**
   * 必填校验
   */
  required?: boolean;
  /**
   * 最小值
   */
  min?: number;
  /**
   * 最大值
   */
  max?: number;
  /**
   * 最小长度
   */
  minLength?: number;
  /**
   * 最大长度
   */
  maxLength?: number;
  /**
   * 最大长度
   */
  range?: [number, number];
  /**
   * 最大长度
   */
  rangeLength?: [number, number];
  /**
   * 数值
   */
  number?: boolean;
  /**
   * 整数
   */
  digits?: boolean;
  /**
   * 小数
   */
  decimal?: boolean;
  /**
   * 银行卡
   */
  bankcard?: boolean;
  /**
   * 身份证
   */
  idcard?: boolean;
  /**
   * 手机
   */
  phone?: boolean;
  /**
   * 电话号码
   */
  tel?: boolean;
  /**
   * 邮政编码
   */
  postcode?: boolean;
  /**
   * 邮箱
   */
  email?: boolean;
  /**
   * 网址
   */
  url?: boolean;
  /**
   * ipv4
   */
  ipv4?: boolean;
  /**
   * ipv4
   */
  qq?: boolean;
};

/**
 * 验证规则
 */
export type Rule = RuleObj[] | RuleObj | Function | string | boolean;

// const keyworks = ['msg'];
export default function validator(data: { [k: string]: any }, rules: { [k: string]: Rule }) {
  const promises = [];
  for (const key in rules) {
    const rule = ruleHandler(rules[key]);
    const value = data[key];
    for (const ruleObj of rule) {
      const { msg } = ruleObj;
      for (const k in ruleObj) {
        const ruleFunc = RULES[k];
        if (typeof ruleFunc !== 'function') {
          continue;
        }
        // 校验
        const res = ruleFunc(value, ruleObj[k], { data, rule });
        if (Array.isArray(res)) {
          res.forEach((x) => {
            const promise = toPromise(x, msg);
            promises.push({ promise, key, rule: k });
          });
        } else {
          const promise = toPromise(res, msg);
          promises.push({ promise, key, rule: k });
        }
      }
    }
  }
  return new Promise<void>((resolve, reject) => {
    for (const promise of promises) {
      promise.promise
        .catch((error: any) => {
          console.log('error', error);
          if (!Array.isArray(error)) {
            error = [error];
          }
          promise.error = error;
        })
        .finally(() => {
          promise.complete = true;
          if (promises.filter((x) => !x.complete).length === 0) {
            const errors = promises.filter((x) => x.error);
            console.log(errors)
            if (errors.length) {
              reject(errors.flatMap((x) => x.error));
            } else {
              resolve();
            }
          }
        });
    }
  });
}

/**
 * 结果转换
 * @param res
 * @param msg
 */
function toPromise(res: Result, msg: string): Promise<void> {
  if (res instanceof Promise) {
    return res.catch((err) => {
      throw err || msg;
    });
  }
  if (typeof res === 'boolean') {
    return res ? Promise.resolve() : Promise.reject(msg);
  }
  return res ? Promise.reject(res) : Promise.resolve();
}

/**
 * 规则处理
 * @param rule
 */
function ruleHandler(rule: Rule): RuleObj[] {
  if (Array.isArray(rule)) {
    return rule as RuleObj[];
  }
  if (typeof rule === 'string') {
    return [{ required: true, msg: rule }];
  }
  if (typeof rule === 'boolean') {
    return [{ required: rule, msg: '必填' }];
  }
  if (typeof rule === 'function') {
    // @ts-ignore
    return [{ validator: rule }];
  }
  if (typeof rule === 'object') {
    return [rule] as RuleObj[];
  }
  // eslint-disable-next-line no-throw-literal
  throw `不支持的规则配置：${rule}`;
}

/**
 * 内置规则
 */
const RULES = {
  array(value: any[], params: { [k: string]: Rule }) {
    return value.map((x) => validator(x, params));
  },
  object(value: any, params: { [k: string]: Rule }) {
    return validator(value, params);
  },
  validator(value: any, params: Function, context: any) {
    return params(value, context);
  },
  pattern(value: any, params: string | RegExp) {
    if (typeof params === 'string') {
      return new RegExp(params).test(value);
    }
    return params.test(value);
  },
  required(value: any, params = true) {
    if (!params) {
      return true;
    }
    if (value === undefined || value === null) {
      return false;
    }
    if (typeof value === 'string') {
      return value.trim() !== '';
    }
    if (Array.isArray(value)) {
      return value.length !== 0;
    }
    if (typeof value === 'object') {
      return Object.keys(value).length !== 0;
    }
    return true;
  },
  min(value: any, params: number) {
    return value >= params;
  },
  max(value: any, params: number) {
    return value <= params;
  },
  minLength(value: any, params: number) {
    return value.length >= params;
  },
  maxLength(value: any, params: number) {
    return value.length <= params;
  },
  range(value: any, params: [number, number]) {
    return RULES.min(value, params[0]) && RULES.max(value, params[1]);
  },
  rangeLength(value: any, params: [number, number]) {
    return RULES.minLength(value, params[0]) && RULES.maxLength(value, params[1]);
  },
  number(value: any, params: boolean) {
    if (value === undefined || value === null) {
      return !params;
    }
    // eslint-disable-next-line no-restricted-globals
    return !isNaN(value);
  },
  digits(value: any, params: boolean) {
    if (value === undefined || value === null) {
      return !params;
    }
    // eslint-disable-next-line no-restricted-globals
    return !isNaN(value) && !String(value).includes('.');
  },
  decimal(value: any, params: boolean) {
    if (value === undefined || value === null) {
      return !params;
    }
    // eslint-disable-next-line no-restricted-globals
    return !isNaN(value) && String(value).includes('.');
  },
  bankcard(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /^([1-9]{1})(\d{14}|\d{18})$/.test(value);
  },
  idcard(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    // 格式校验
    if (!/\d{17}[\d|x]|\d{15}/i.test(value)) {
      return false;
    }
    // 生日校验
    if (!/^(19\d{2}|20\d{2})(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])$/.test(value.substring(6, 14))) {
      return false;
    }
    // 省地址码校验
    if (
      '11,12,13,14,15,21,22,23,31,32,33,34,35,36,37,41,42,43,44,45,46,50,51,52,53,54,61,62,63,64,65,71,81,82,91'.indexOf(
        value.substring(0, 2),
      ) === -1
    ) {
      return false;
    }
    // 校验和校验
    if (value.length === 18) {
      value = value.split('');
      const d = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
      let f = 0;
      for (let g = 0, h = 0, i = 0; i < 17; i++) {
        g = value[i];
        h = d[i];
        f += g * h;
      }
      const l = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
      if (l[f % 11] !== value[17]) {
        return false;
      }
    }
    return true;
  },
  phone(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /^1[3-9]\d{9}$/.test(value);
  },
  tel(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /[0-9-()（）]{7,18}/.test(value);
  },
  postcode(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /\d{6}/.test(value);
  },
  email(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/.test(value);
  },
  url(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(value);
  },
  ipv4(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)/.test(
      value,
    );
  },
  qq(value: any, params: boolean) {
    if (!value) {
      return !params;
    }
    return /[1-9]([0-9]{5,11})/.test(value);
  },
};

validator(
  {
    k: [{ kkk: 1, idcard: '452724199112313136', dasd: { xxx: '15078104650' } }],
  },
  {
    k: {
      array: {
        kkk: {
          min: 2,
          required: true,
          msg: 'asda',
        },
        idcard: {
          idcard: true,
          msg: '请输入有效的身份证号码',
        },
        dasd: {
          object: {
            xxx: { phone: true },
          },
        },
      },
    },
  },
)
  .then((r) => {
    console.log('ok', 12314);
  })
  .catch((err) => {
    console.log('err', err);
  });
